package edu.utd.text.game.tests;

import java.util.ArrayList;

import junit.framework.Assert;
import junit.framework.TestCase;
import edu.utd.text.game.domain.GameObject;
import edu.utd.text.game.domain.areas.Direction;
import edu.utd.text.game.domain.areas.Room;
import edu.utd.text.game.domain.exceptions.DeadEndEncounteredException;
import edu.utd.text.game.domain.exceptions.DuplicateGameObjectException;
import edu.utd.text.game.domain.exceptions.GameObjectNotFoundException;
import edu.utd.text.game.domain.exceptions.InvalidDirectionException;
import edu.utd.text.game.domain.exceptions.ItemAlreadyExistsInInventoryException;
import edu.utd.text.game.domain.exceptions.MaxItemsExceededException;
import edu.utd.text.game.domain.item.GameItem;
import edu.utd.text.game.domain.player.Inventory;
import edu.utd.text.game.domain.player.Player;
import edu.utd.text.game.tests.mocks.FakeAxe;
import edu.utd.text.game.tests.mocks.FakeKnife;
import edu.utd.text.game.tests.mocks.FakeShovel;

public class TestPlayer extends TestCase {
	
	public void testCreateNewPlayer() {
		Player player = new Player();
		System.out.println("Testing creation of new Player object, new player score=" + player.getScore());
		Assert.assertEquals(player.getScore(), 0);
		System.out.println("Player inventory=" + player.getInventory());
	}
	
	public void testAddItemsToInventory() {
		String playerName = "Troy";
		Player player = new Player(playerName);
		
		Assert.assertEquals(playerName, player.getName());
		
		boolean caughtExpectedException = false;
		
		try {			
			player.setMaxItems(2);
			//try to add too many items: should handle the exception
			player.addItemToInventory(new FakeShovel());
			player.addItemToInventory(new FakeAxe());
			player.addItemToInventory(new FakeKnife());
			
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			fail("WRONG: Something went wrong and we think this item already exists...");
		} catch (MaxItemsExceededException miex) {
			caughtExpectedException = true;
		}
		//did we handle the exception?
		Assert.assertTrue(caughtExpectedException);
		
		//try to add a duplicate
		caughtExpectedException = false;
		try {
			player.setMaxItems(500);
			player.setInventory(new Inventory());
			player.addItemToInventory(new FakeShovel());
			player.addItemToInventory(new FakeShovel()); 
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			caughtExpectedException = true;
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Something went wrong and we think we exceded max allowable items for player...but we didn't");
		}
		
		//now try it with same instance of the inventory object in question
		caughtExpectedException = false;
		try {
			FakeAxe fakeAxe = new FakeAxe();
			player.setMaxItems(500);
			player.setInventory(new Inventory());
			player.addItemToInventory(fakeAxe);
			player.addItemToInventory(fakeAxe); 
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			caughtExpectedException = true;
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Something went wrong and we think we exceded max allowable items for player...but we didn't");
		}
	}
	
	public void testRemoveItemFromInventory() {
		Player player = new Player();
		try {
			//clear any previous inventory
			Inventory inv = new Inventory();
			player.setInventory(inv);
	
			//create a new game item to add
			
			//test the case of successful removal
			GameItem shovel = new FakeShovel();
			player.addItemToInventory(shovel);
			boolean successfullyRemoved = player.removeItemFromInventory(shovel);
			
			if(!successfullyRemoved)
				fail("WRONG: We did not remove the item from inventory correctly (method call returned failure).");
			
			if(player.getInventory().getNumberOfItems() != 0)
				fail("WRONG: We did not remove the item from inventory correctly (method call returned success but it didn't work).");
		
			//test the case of unsuccessful removal (item not found in inventory)
			player.setInventory(new Inventory());
			GameItem axe = new FakeAxe();
			player.addItemToInventory(axe);
			
			if(player.getInventory().getNumberOfItems() != 1)
				fail("WRONG: We did not successfully add the item to inventory, so we can't test removing it.");
			
			successfullyRemoved = player.removeItemFromInventory(new FakeShovel());
			
			//removal should have failed
			if(successfullyRemoved)
				fail("WRONG: Removal of the inventory item should have returned failure (false) but it didn't");
			if(player.getInventory().getNumberOfItems() == 0) //did we go back down from 1 to 0? we shouldn't have
				fail("WRONG: Removal of inventory item should have failed, but for some reason it was still removed "
						+ "(even though is said it was successfully removed)");
			
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			fail("WRONG: Something went wrong and we think this item already exists...");	
		} catch (MaxItemsExceededException miex) {
			fail("WRONG: We got an unexpected MaxItemsExceededException");
		}	
	}
	
	public void testAddDuplicateItemToInventory() {
		Player player = new Player();
		try {
			//also tests lazy initialization of inventory if we 
			//don't explicity provide it - inv should not be null
			//here even though we didn't explicitly set it
			player.addItemToInventory(new FakeShovel());
			player.addItemToInventory(new FakeShovel());
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			System.out.println("Found the expected duplicate in the inventory!");
			return;
		} catch (MaxItemsExceededException miex) {
			fail("WRONG: We got an unexpected MaxItemsExceededException");
		}
		//should not have gotten here
		fail("WRONG: Did not catch the duplicate inventory item exception");
	}
	
	//test moving a player through a room and picking up an object that is in the second room...
	public void testMovement() {
		
		//create 2 rooms (living room & dining room) connected to each other, each room with one exit
		Room livingRoom = new Room("Living Room");	//dining room contains no game objects
		//create an ArrayList of stuff that will be in the room
		ArrayList<GameObject> diningRoomObjects = new ArrayList<GameObject>();
		diningRoomObjects.add(new FakeShovel());
		diningRoomObjects.add(new FakeAxe());
		diningRoomObjects.add(new FakeKnife());
		
		Room diningRoom = new Room(diningRoomObjects);
		
		//connect the 2 rooms
		livingRoom.setNorthExit(diningRoom);
		diningRoom.setSouthExit(livingRoom);
		
		Player player = new Player();
		player.setCurrentRoom(livingRoom);
		
		//player moves into the dining room (north)
		try {
			player.moveDirection(Direction.NORTH);
			Assert.assertTrue(player.getCurrentRoom().equals(diningRoom));
		} catch (DeadEndEncounteredException deex) {
			deex.printStackTrace();
			fail("ENCOUNTERED unexpected DeadEndEncounteredException.  " +  
					"Could not move the player into the room to the north");
		} catch (InvalidDirectionException idx) {
			idx.printStackTrace();
			fail("ENCOUNTERED unexpected InvalidDirectionException.  Something is wrong...");  
		}
		
		//try going into a known dead end - should get exception
		boolean caughtExpectedException = false;
		try {
			player.moveDirection(Direction.NORTHEAST);
		} catch (DeadEndEncounteredException deex) {
			System.out.println("CAUGHT expected Dead End exception.  Good.");
			caughtExpectedException = true;
		} catch (InvalidDirectionException idx) {
			idx.printStackTrace();
			fail("ENCOUNTERED unexpected InvalidDirectionException.  Something is wrong...");
		}
		
		if(!caughtExpectedException) fail("WRONG: We did not catch a dead end exception where there should be a dead end");
		//make sure we didn't go anywhere when trying to go to a dead end - should still be in same room
		Assert.assertTrue(player.getCurrentRoom().equals(diningRoom));
		
		//try to get a shovel from the dining room
		try {
			player.pickupItem(new FakeShovel());
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Should not have encountered an ItemAlreadyExists... exception here.");
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Should have encountered MaxItemsExceded here.");
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered an unexpected GameObjectNotFoundException!");
		}
		
		Assert.assertTrue(player.getInventory().contains(new FakeShovel()));
		Assert.assertTrue(player.hasItemOfType(FakeShovel.class));
		
		//go back to the living room, create another shovel instance here and try to pick it up
		//it should not be allowed because the player is already carrying one
		
		try {
			player.moveDirection(Direction.SOUTH);
			Assert.assertTrue(player.getCurrentRoom().equals(livingRoom));
		} catch (DeadEndEncounteredException deex) {
			deex.printStackTrace();
			fail("WRONG: Encountered unexpected dead end");
		} catch (InvalidDirectionException idx) {
			idx.printStackTrace();
			fail("WRONG: Encountered unexpected InvalidDirectionException.");
		}
		
		try {
			livingRoom.addGameObject(new FakeShovel());
		} catch (DuplicateGameObjectException dgox) {
			dgox.printStackTrace();
			fail("WRONG: Encountered unexpected DuplicateGameObjectException when trying to add a unique obj to livingRoom");
		}
		
		caughtExpectedException = false;
		try {
			player.pickupItem(new FakeShovel());
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameObjectNotFound... but the object should be there.");
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected inventory full exception");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			//we should get here
			caughtExpectedException = true;
		} 
		Assert.assertTrue(caughtExpectedException);
		Assert.assertTrue(player.hasItemOfType((new FakeShovel()).getClass())); 
	}		
	
	public void testScoring() {
		Player player = new Player();
		//starting score should be zero
		Assert.assertEquals(0, player.getScore());
		player.setScore(5);
		Assert.assertEquals(5, player.getScore());
		player.incrementScoreBy(10);
		Assert.assertEquals(15, player.getScore());
		player.decrementScoreBy(4);
		Assert.assertEquals(11, player.getScore());
	}
	
	  ///////////////////////////
	 // Explicit method tests //
	///////////////////////////
	
	public void testCanHoldMoreItems() {
		try {
			Player player = new Player();
			Assert.assertTrue(player.canHoldMoreItems());
			player.addItemToInventory(new FakeAxe());
			Assert.assertTrue(player.hasItem(new FakeAxe()));	
			player.setMaxItems(1);
			Assert.assertTrue(!player.canHoldMoreItems());
		} catch (Exception e) {
			e.printStackTrace();
			fail("WRONG: Player.canHoldMoreItems() is busted");
		}
	}
	
	public void testHasItemOfType()  {
		Player player = new Player();
		Assert.assertTrue(!player.hasItemOfType(FakeShovel.class)); 
		try { player.addItemToInventory(new FakeShovel()); } 
			catch(Exception e) { fail("WRONG: Now why did this happen...we already tested this stuff..."); }
		boolean foundAnItem = player.hasItemOfType(FakeShovel.class); 
			Assert.assertTrue(foundAnItem);
	}
	
	public void testHasItemParamGameItem() {
		Player player = new Player();
		Assert.assertTrue(!player.hasItem(new FakeShovel()));
		try {
			player.addItemToInventory(new FakeShovel());
		} catch (Exception e) {
			e.printStackTrace();
			fail("This shouldn't have happened");
		}
		Assert.assertTrue(player.hasItem(new FakeShovel().getName()));
		Assert.assertTrue(!player.hasItem(new FakeAxe().getName()));
	}
	
	public void testHasItemParamString() {
		Player player = new Player();
		Assert.assertTrue(!player.hasItem(new FakeShovel()));
		try {
			player.addItemToInventory(new FakeShovel());
		} catch (Exception e) {
			e.printStackTrace();
			fail("This shouldn't have happened");
		}
		Assert.assertTrue(player.hasItem(new FakeShovel()));
		Assert.assertTrue(!player.hasItem(new FakeAxe()));

	}
	
	//test overloaded method pickupItem(GameItem) 
	public void testPickupItemParamGameItem() {
		Player player = new Player();
		//what happens if we try pickup before a player is assoc w/ a room?
		boolean handledExpectedException = false;
		try {
			player.pickupItem(new FakeShovel());
		} catch (GameObjectNotFoundException gonfx) {
			handledExpectedException = true;
			System.out.println("CAUGHT an expected GameObjectNotFoundException from pickupItem() method.");
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
			
		Assert.assertTrue(handledExpectedException );
		
		//now try to add a room with some stuff in it and see if we can pick it up correctly
		//implicity test inventory max items and unique item rules here as well.
		Room hallWay = new Room("Hallway");
		player.setCurrentRoom(hallWay);
		//put an interesting item in the room
		try { hallWay.addGameObject(new FakeShovel()); } catch (Exception e) { 
			e.printStackTrace();
			fail("WRONG: Now why did this happen...we already tested this stuff..."); }
		try {
			player.pickupItem(new FakeShovel()); 
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
		
		
		//did we pickup the item successfully?
		Assert.assertTrue(player.hasItemOfType(FakeShovel.class));
		
		//try to pickup another one and we should get an error
		boolean caughtExpectedException = false;
		try { 
			//we already took the previous one out of the room and now we have it 
			//in player inventory, so put another one into the room for this to work
			try { hallWay.addGameObject(new FakeShovel()); } catch (Exception e) { e.printStackTrace(); fail("Whoa, something unexpected went wrong...");	}
			player.pickupItem(new FakeShovel());
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			caughtExpectedException = true; 
		}
		
		Assert.assertTrue(caughtExpectedException);
		System.out.println("Successfully handled a duplicate inventory item...");
		
		//Next try it with the exact same instance
		player = new Player();
		hallWay = new Room("Hallway");
		FakeShovel fakeShovel = new FakeShovel();
		player.setCurrentRoom(hallWay);
		try { hallWay.addGameObject(fakeShovel); } catch (Exception e) {
			e.printStackTrace();
			fail("WRONG: Now why did this happen...we already tested this stuff..."); }
		try {
			player.pickupItem(fakeShovel);
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
		//if everything went fine we should now have ourselves a fakeShovel
		Assert.assertTrue(player.hasItem(fakeShovel));
		//but this should also work
		Assert.assertTrue(player.hasItem(new FakeShovel()));
		//and so should this
		Assert.assertTrue(player.hasItemOfType(fakeShovel.getClass()));
		System.out.println("Player Inventory contains: (should be 1 thing) ::\n"  + player.getInventory());
	}
	
	public void testPickupItemParamClass() {
		Player player = new Player();
		//what happens if we try pickup before a player is assoc w/ a room?
		boolean handledExpectedException = false;
		try {
			player.pickupItem(new FakeShovel());
		} catch (GameObjectNotFoundException gonfx) {
			handledExpectedException = true;
			System.out.println("CAUGHT an expected GameObjectNotFoundException from pickupItem(Class) method.");
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
			
		Assert.assertTrue(handledExpectedException );
		
		//now try to add a room with some stuff in it and see if we can pick it up correctly
		//implicity test inventory max items and unique item rules here as well.
		Room hallWay = new Room("Hallway");
		player.setCurrentRoom(hallWay);
		//put an interesting item in the room
		try { hallWay.addGameObject(new FakeShovel()); } catch (Exception e) { 
			e.printStackTrace();
			fail("WRONG: Now why did this happen...we already tested this stuff..."); }
		try {
			player.pickupItem(new FakeShovel()); 
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
		
		
		//did we pickup the item successfully?
		Assert.assertTrue(player.hasItemOfType(FakeShovel.class));
		
		//try to pickup another one and we should get an error
		boolean caughtExpectedException = false;
		try { 
			//we already took the previous one out of the room and now we have it 
			//in player inventory, so put another one into the room for this to work
			try { hallWay.addGameObject(new FakeShovel()); } catch (Exception e) { e.printStackTrace(); fail("Whoa, something unexpected went wrong...");	}
			player.pickupItem(new FakeShovel());
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			caughtExpectedException = true; 
		}
		
		Assert.assertTrue(caughtExpectedException);
		System.out.println("Successfully handled a duplicate inventory item...");
		
		//Next try it with the exact same instance
		player = new Player();
		hallWay = new Room("Hallway");
		FakeShovel fakeShovel = new FakeShovel();
		player.setCurrentRoom(hallWay);
		try { hallWay.addGameObject(fakeShovel); } catch (Exception e) {
			e.printStackTrace();
			fail("WRONG: Now why did this happen...we already tested this stuff..."); }
		try {
			player.pickupItem(fakeShovel);
		} catch (GameObjectNotFoundException gonfx) {
			gonfx.printStackTrace();
			fail("WRONG: Encountered unexpected GameItemNotFoundException when trying to pickup an object"); 
		} catch (MaxItemsExceededException miex) {
			miex.printStackTrace();
			fail("WRONG: Encountered unexpected MaxItems exception when trying to pickup an object");
		} catch (ItemAlreadyExistsInInventoryException iaeiix) {
			iaeiix.printStackTrace();
			fail("WRONG: Encountered unexpected ItemAlreadyExists exception when trying to pickup an object"); }
		//if everything went fine we should now have ourselves a fakeShovel
		Assert.assertTrue(player.hasItem(fakeShovel));
		//but this should also work
		Assert.assertTrue(player.hasItem(new FakeShovel()));
		//and so should this
		Assert.assertTrue(player.hasItemOfType(fakeShovel.getClass()));
		System.out.println("Player Inventory contains: (should be 1 thing) ::\n"  + player.getInventory());	
	}
	
	
	public void testDropItemInRoom() {
		Player player = new Player();
		Room room = new Room("Room");
		player.setCurrentRoom(room);
		
		try { player.addItemToInventory(new FakeAxe()); } catch (Exception x) { x.printStackTrace(); fail("UNEXPECTED exception..."); }
		
		Assert.assertTrue(player.dropItemInRoom(new FakeAxe().getName()));	//returns boolean success/fail
		Assert.assertTrue(!player.hasItem(new FakeAxe()));
		Assert.assertTrue(room.containsGameObjectOfType((new FakeAxe()).getClass()));
		
		//test case where player tries to drop item in a room where that item already exists
		
		try { 
			player.pickupItem(new FakeAxe()); 
			room.addGameObject(new FakeAxe());
		} catch (Exception x) { x.printStackTrace(); fail("UNEXPECTED exception..."); }
		
		Assert.assertTrue(!player.dropItemInRoom("fake ax"));	//should use the tagging mechanism to figure out that I mean "FakeAxe" 
		//and it should fail because there's already a FakeAxe in there.
		
	}
}
